Progressive Hydration やってみた(途中の話)
Progressive Hydration
Google I/O 2019 で出てた SSR の新しいアプローチ
参考動画
https://www.youtube.com/watch?v=k-A2VfuUROg
3行
1. SSR は愚直に render
2. CSR で初回では不要なコンポーネントで shouldComponenUpdate -> false
3. 必要になった時に 2 で描画した空 div に ReactDOM.hydrate でマウント
kwsk 1-1
code:tsx
import { ClientHydrator, ServerHydrator } from "./Hydrator";
let load = () => import("./LazyHydrateComponent");
let Hydrator: React.ComponentType<any> = ClientHydrator;
if (!process.browser /* サーバ判定なら何でも */) {
Hydrator = ServerHydrator;
load = () => require("./LazyHydrateComponent");
}
export const App: React.FC = () => {
return (
<>
<Header>
<LongList />
<Hydrator load={load} />
<footer>
<>
);
}
// LazyHydrateComponent.tsx
export const LazyHydrateComponent: React.FC = () => {
// 雑に
if (process.browser) {
const handler = () => someFn();
return <div onClick={handler}>rehydrate</div>;
}
return <div>ssr</div>;
}
kwsk 1-2
code:tsx
// ESM, CJS 相互運用クッション関数
function interopDefault(mod) {
return mod && mod.default || mod;
}
export const ServerHydrator = ({load, ...props}) => {
const Child = interopDefault(load());
return (
<><Child {...props} /></>
);
};
kwsk 2, 3
code:tsx
export class ClientHydrator extends React.Component<any> {
private root: HTMLDivElement | null = null;
public shouldComponentUpdate() {
return false;
}
public componentDidMount() {
window.addEventListener("some:custom:event", async () => {
const { load, ...props } = this.props;
const Child = interopDefault(await load());
ReactDOM.hydrate(<Child {...props}/>, this.root);
});
}
public render() {
return (
<div
ref={el => this.root = el}
dangerouslySetInnerHTML={{__html: ""}}
suppressHydrationWarning
/>
);
}
}
kwsk re-hydrate timing
code:tsx
// hydrate したいタイミングで
const event = new CustomEvent("some:custom:event");
window.dispatchEvent(event);
こんな時に使えそう
SSR で DOM は完全に描画された
でも、{見えていない|インタラクションが起きていない}ので
ハンドラのアタッチは遅延させたい
仮想DOMの構築は後でやりたい
良さそうじゃん
SSR & 初回 CSR 後はこうなる
React Devtools(※ イメージ)
code:html
<App>
<Header>
<h1>タイトル</h1>
</Header>
<Hydrator>
<!-- 実DOM: <div>ssr</div> -->
<div></div>
</Hydrator>
<Footer>
<p>some footer</p>
</Footer>
</App>
CSR 後にrehydrate
React Devtools(※ イメージ)
code:html
<App>
<Header>
<h1>タイトル</h1>
</Header>
<Hydrator>
<div></div><!-- 仮想DOM上は空 -->
</Hydrator>
<Footer>
<p>some footer</p>
</Footer>
</App>
<!-- すでに構築された実DOM(ref でつくったやつ)にマウントされる -->
<LazyHydrateComponent>
<!-- 実DOM: <div>rehydrate</div>, ハンドラがアタッチされる -->
<div>rehydrate</div>
</LazyHydrateComponent>
別の仮想DOMツリーなので
Redux と食い合わせ悪そうな気がしてきた
StoreProvider がいないので StoreProvider 2つ必要?
あまり良策を考えられていない
終わり